{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Illustration of experiments\n",
    "These experiments are similar in nature to what we presented in our L4DC paper (results there are averaged across multiple seeds, and collected data in a different manner).\n",
    "We compare a simple MLP to two variations of our differentiable and learnable robot model on simulated data."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import torch\n",
    "import random\n",
    "import os\n",
    "from tqdm import tqdm\n",
    "\n",
    "from hydra.experimental import compose as hydra_compose\n",
    "from hydra.experimental import initialize_config_dir\n",
    "\n",
    "from differentiable_robot_model.differentiable_robot_model import DifferentiableRobotModel\n",
    "from differentiable_robot_model.data_generation_utils import generate_sine_motion_inverse_dynamics_data\n",
    "from torch.utils.data import DataLoader\n",
    "\n",
    "torch.set_printoptions(precision=3, sci_mode=False)\n",
    "\n",
    "random.seed(0)\n",
    "np.random.seed(1) # seeds the data generation\n",
    "torch.manual_seed(0)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Normalized Mean-Squared Error Loss\n",
    "class NMSELoss(torch.nn.Module):\n",
    "    def __init__(self, var):\n",
    "        super(NMSELoss, self).__init__()\n",
    "        self.var = var\n",
    "    def forward(self, yp, yt):\n",
    "        err = (yp - yt)**2\n",
    "        werr = err/self.var\n",
    "        return werr.mean()\n",
    "\n",
    "# Simple Multi-Layer Perceptron \n",
    "class SimpleMLP(torch.nn.Module):\n",
    "    def __init__(self, in_dim, out_dim):\n",
    "        super(SimpleMLP, self).__init__()\n",
    "        self.in_dim = in_dim\n",
    "        self.out_dim = out_dim\n",
    "        self.layers = torch.nn.Sequential(\n",
    "            torch.nn.Linear(self.in_dim, 250),\n",
    "            torch.nn.ReLU(),\n",
    "            torch.nn.Linear(250, 250),\n",
    "            torch.nn.ReLU(),\n",
    "            torch.nn.Linear(250, 100),\n",
    "            torch.nn.ReLU(),\n",
    "            torch.nn.Linear(100, self.out_dim),\n",
    "        )\n",
    "\n",
    "    def forward(self, q, qd, qdd_des):\n",
    "        x = torch.cat([q, qd, qdd_des], dim=1)\n",
    "        y = self.layers(x)\n",
    "        return y"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Loading of Robot Model (kuka iiwa)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "\n",
    "abs_config_dir=os.path.abspath(\"../conf\")\n",
    "with initialize_config_dir(config_dir=abs_config_dir):\n",
    "    # we load a ground truth robot model (with the correct parameters) to generate data\n",
    "    gt_robot_model_cfg = hydra_compose(config_name=\"torch_robot_model_gt.yaml\")\n",
    "    # and 2 versions of a learnable robot model\n",
    "    # without physical constraints on rigid body params\n",
    "    learnable_robot_no_constraints_cfg = hydra_compose(config_name=\"torch_robot_model_learnable_l4dc_no_constraints.yaml\")\n",
    "    # with physical constraints on rigid body params\n",
    "    learnable_robot_constraints_cfg = hydra_compose(config_name=\"torch_robot_model_learnable_l4dc_constraints.yaml\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Training\n",
    "2 training routines for training the robot model, and for training a MLP"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": [
    "\n",
    "def train_robot_model(robot_model, train_data, test_data, lr, n_epochs, debug_print=False):\n",
    "    train_loader = DataLoader(dataset=train_data, batch_size=256, shuffle=False)\n",
    "    loss_fn = NMSELoss(train_data.var())\n",
    "    opt = torch.optim.Adam(robot_model.parameters(), lr=1e-2)\n",
    "    train_loss = []\n",
    "    test_loss= []\n",
    "    for i in range(50):\n",
    "        losses = []\n",
    "        for batch_idx, batch_data in enumerate(tqdm(train_loader)):\n",
    "            q, qd, qdd_des, tau = batch_data\n",
    "            opt.zero_grad()\n",
    "            tau_pred = robot_model.compute_inverse_dynamics(q, qd, qdd_des)\n",
    "            loss = loss_fn(tau_pred, tau)\n",
    "            loss.backward()\n",
    "            opt.step()\n",
    "            losses.append(loss.item())\n",
    "            \n",
    "        train_loss.append(np.mean(losses))\n",
    "        if debug_print:\n",
    "            print(\"epoch: {}, loss: {}\".format(i, train_loss[-1]))\n",
    "        # test\n",
    "        test_q, test_qd, test_qdd_des, test_tau = test_data.data['q'], test_data.data['qd'], test_data.data['qdd_des'], test_data.data['tau']\n",
    "        test_tau_pred = robot_model.compute_inverse_dynamics(test_q, test_qd, test_qdd_des)\n",
    "        test_loss.append(loss_fn(test_tau_pred, test_tau).item())\n",
    "    return train_loss, test_loss\n",
    "\n",
    "def train_mlp(model, train_data, test_data, lr, n_epochs, debug_print=False):\n",
    "    train_loader = DataLoader(dataset=train_data, batch_size=256, shuffle=False)\n",
    "    loss_fn = NMSELoss(train_data.var())\n",
    "    opt = torch.optim.Adam(model.parameters(), lr=lr)\n",
    "    train_loss = []\n",
    "    test_loss= []\n",
    "    for i in range(n_epochs):\n",
    "        losses = []\n",
    "        for batch_idx, batch_data in enumerate(tqdm(train_loader)):\n",
    "            q, qd, qdd_des, tau = batch_data\n",
    "            opt.zero_grad()\n",
    "            tau_pred = model(q, qd, qdd_des)\n",
    "            loss = loss_fn(tau_pred, tau)\n",
    "            loss.backward()\n",
    "            opt.step()\n",
    "            losses.append(loss.item())\n",
    "            \n",
    "        train_loss.append(np.mean(losses))\n",
    "        if debug_print:\n",
    "            print(\"epoch: {}, loss: {}\".format(i, train_loss[-1]))\n",
    "        # test\n",
    "        test_q, test_qd, test_qdd_des, test_tau = test_data.data['q'], test_data.data['qd'], test_data.data['qdd_des'], test_data.data['tau']\n",
    "        test_tau_pred = model(test_q, test_qd, test_qdd_des)\n",
    "        test_loss.append(loss_fn(test_tau_pred, test_tau).item())\n",
    "    return train_loss, test_loss\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Data Generation\n",
    "we use the ground truth robot model to generate training and test data. Here we generate data along a trajectory (sine motion trajectory) - which is how we would typically collect data on a real robot."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Unknown tag: material\n",
      "Unknown tag: self_collision_checking\n",
      "Unknown tag: material\n",
      "Unknown tag: material\n",
      "Unknown tag: material\n",
      "Unknown tag: material\n",
      "Unknown tag: material\n",
      "Unknown tag: material\n",
      "Unknown tag: material\n",
      "Unknown tag: hardwareInterface\n",
      "Unknown tag: robotNamespace\n",
      "Unknown tag: hardwareInterface\n",
      "Unknown tag: robotNamespace\n",
      "Unknown tag: hardwareInterface\n",
      "Unknown tag: robotNamespace\n",
      "Unknown tag: hardwareInterface\n",
      "Unknown tag: robotNamespace\n",
      "Unknown tag: hardwareInterface\n",
      "Unknown tag: robotNamespace\n",
      "Unknown tag: hardwareInterface\n",
      "Unknown tag: robotNamespace\n",
      "Unknown tag: hardwareInterface\n",
      "Unknown tag: robotNamespace\n"
     ]
    }
   ],
   "source": [
    "gt_robot_model = DifferentiableRobotModel(**gt_robot_model_cfg.model)\n",
    "train_data = generate_sine_motion_inverse_dynamics_data(gt_robot_model, n_data=10000, dt=1.0/250.0, freq=0.1)\n",
    "test_data = generate_sine_motion_inverse_dynamics_data(gt_robot_model, n_data=10000, dt=1.0/250.0, freq=0.2)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Training\n",
    "We now train all 3 models (MLP, robot model without physical constraints and robot model with physical constraints)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "100%|██████████| 40/40 [00:00<00:00, 120.30it/s]\n",
      "100%|██████████| 40/40 [00:00<00:00, 139.16it/s]\n",
      "100%|██████████| 40/40 [00:00<00:00, 121.71it/s]\n",
      "100%|██████████| 40/40 [00:00<00:00, 129.77it/s]\n",
      "100%|██████████| 40/40 [00:00<00:00, 124.38it/s]\n",
      "100%|██████████| 40/40 [00:00<00:00, 121.36it/s]\n",
      "100%|██████████| 40/40 [00:00<00:00, 119.80it/s]\n",
      "100%|██████████| 40/40 [00:00<00:00, 103.83it/s]\n",
      "100%|██████████| 40/40 [00:00<00:00, 111.53it/s]\n",
      "100%|██████████| 40/40 [00:00<00:00, 126.61it/s]\n",
      "100%|██████████| 40/40 [00:00<00:00, 119.02it/s]\n",
      "100%|██████████| 40/40 [00:00<00:00, 120.07it/s]\n",
      "100%|██████████| 40/40 [00:00<00:00, 119.31it/s]\n",
      "100%|██████████| 40/40 [00:00<00:00, 127.45it/s]\n",
      "100%|██████████| 40/40 [00:00<00:00, 130.54it/s]\n",
      "100%|██████████| 40/40 [00:00<00:00, 126.32it/s]\n",
      "100%|██████████| 40/40 [00:00<00:00, 135.26it/s]\n",
      "100%|██████████| 40/40 [00:00<00:00, 136.15it/s]\n",
      "100%|██████████| 40/40 [00:00<00:00, 132.46it/s]\n",
      "100%|██████████| 40/40 [00:00<00:00, 135.48it/s]\n",
      "100%|██████████| 40/40 [00:00<00:00, 126.08it/s]\n",
      "100%|██████████| 40/40 [00:00<00:00, 134.96it/s]\n",
      "100%|██████████| 40/40 [00:00<00:00, 135.52it/s]\n",
      "100%|██████████| 40/40 [00:00<00:00, 126.21it/s]\n",
      "100%|██████████| 40/40 [00:00<00:00, 136.86it/s]\n",
      "100%|██████████| 40/40 [00:00<00:00, 134.52it/s]\n",
      "100%|██████████| 40/40 [00:00<00:00, 132.51it/s]\n",
      "100%|██████████| 40/40 [00:00<00:00, 133.31it/s]\n",
      "100%|██████████| 40/40 [00:00<00:00, 125.15it/s]\n",
      "100%|██████████| 40/40 [00:00<00:00, 134.28it/s]\n",
      "100%|██████████| 40/40 [00:00<00:00, 135.02it/s]\n",
      "100%|██████████| 40/40 [00:00<00:00, 124.71it/s]\n",
      "100%|██████████| 40/40 [00:00<00:00, 132.98it/s]\n",
      "100%|██████████| 40/40 [00:00<00:00, 132.76it/s]\n",
      "100%|██████████| 40/40 [00:00<00:00, 134.44it/s]\n",
      "100%|██████████| 40/40 [00:00<00:00, 121.82it/s]\n",
      "100%|██████████| 40/40 [00:00<00:00, 130.83it/s]\n",
      "100%|██████████| 40/40 [00:00<00:00, 128.50it/s]\n",
      "100%|██████████| 40/40 [00:00<00:00, 116.47it/s]\n",
      "100%|██████████| 40/40 [00:00<00:00, 128.02it/s]\n",
      "100%|██████████| 40/40 [00:00<00:00, 129.89it/s]\n",
      "100%|██████████| 40/40 [00:00<00:00, 131.69it/s]\n",
      "100%|██████████| 40/40 [00:00<00:00, 132.50it/s]\n",
      "100%|██████████| 40/40 [00:00<00:00, 119.45it/s]\n",
      "100%|██████████| 40/40 [00:00<00:00, 125.38it/s]\n",
      "100%|██████████| 40/40 [00:00<00:00, 130.82it/s]\n",
      "100%|██████████| 40/40 [00:00<00:00, 121.69it/s]\n",
      "100%|██████████| 40/40 [00:00<00:00, 126.64it/s]\n",
      "100%|██████████| 40/40 [00:00<00:00, 128.17it/s]\n",
      "100%|██████████| 40/40 [00:00<00:00, 130.78it/s]\n"
     ]
    }
   ],
   "source": [
    "# the mlp seems to train better with lr=1e-3 (while for robot models we use 1e-2)\n",
    "simple_mlp = SimpleMLP(in_dim=21, out_dim=7)\n",
    "train_loss_mlp, test_loss_mlp = train_mlp(simple_mlp, train_data, test_data, lr=1e-3, n_epochs=50)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Unknown tag: material\n",
      "Unknown tag: self_collision_checking\n",
      "Unknown tag: material\n",
      "Unknown tag: material\n",
      "Unknown tag: material\n",
      "Unknown tag: material\n",
      "Unknown tag: material\n",
      "Unknown tag: material\n",
      "Unknown tag: material\n",
      "Unknown tag: hardwareInterface\n",
      "Unknown tag: robotNamespace\n",
      "Unknown tag: hardwareInterface\n",
      "Unknown tag: robotNamespace\n",
      "Unknown tag: hardwareInterface\n",
      "Unknown tag: robotNamespace\n",
      "Unknown tag: hardwareInterface\n",
      "Unknown tag: robotNamespace\n",
      "Unknown tag: hardwareInterface\n",
      "Unknown tag: robotNamespace\n",
      "Unknown tag: hardwareInterface\n",
      "Unknown tag: robotNamespace\n",
      "Unknown tag: hardwareInterface\n",
      "Unknown tag: robotNamespace\n",
      "100%|██████████| 40/40 [00:02<00:00, 14.04it/s]\n",
      "100%|██████████| 40/40 [00:02<00:00, 14.29it/s]\n",
      "100%|██████████| 40/40 [00:02<00:00, 13.92it/s]\n",
      "100%|██████████| 40/40 [00:02<00:00, 14.35it/s]\n",
      "100%|██████████| 40/40 [00:02<00:00, 13.36it/s]\n",
      "100%|██████████| 40/40 [00:02<00:00, 14.15it/s]\n",
      "100%|██████████| 40/40 [00:02<00:00, 13.81it/s]\n",
      "100%|██████████| 40/40 [00:02<00:00, 13.70it/s]\n",
      "100%|██████████| 40/40 [00:02<00:00, 13.94it/s]\n",
      "100%|██████████| 40/40 [00:02<00:00, 13.88it/s]\n",
      "100%|██████████| 40/40 [00:03<00:00, 13.32it/s]\n",
      "100%|██████████| 40/40 [00:03<00:00, 12.92it/s]\n",
      "100%|██████████| 40/40 [00:02<00:00, 13.38it/s]\n",
      "100%|██████████| 40/40 [00:03<00:00, 13.13it/s]\n",
      "100%|██████████| 40/40 [00:03<00:00, 13.31it/s]\n",
      "100%|██████████| 40/40 [00:03<00:00, 13.09it/s]\n",
      "100%|██████████| 40/40 [00:03<00:00, 13.30it/s]\n",
      "100%|██████████| 40/40 [00:02<00:00, 13.40it/s]\n",
      "100%|██████████| 40/40 [00:03<00:00, 12.97it/s]\n",
      "100%|██████████| 40/40 [00:03<00:00, 13.01it/s]\n",
      "100%|██████████| 40/40 [00:03<00:00, 13.02it/s]\n",
      "100%|██████████| 40/40 [00:02<00:00, 13.44it/s]\n",
      "100%|██████████| 40/40 [00:02<00:00, 13.56it/s]\n",
      "100%|██████████| 40/40 [00:03<00:00, 13.15it/s]\n",
      "100%|██████████| 40/40 [00:02<00:00, 13.61it/s]\n",
      "100%|██████████| 40/40 [00:02<00:00, 13.77it/s]\n",
      "100%|██████████| 40/40 [00:02<00:00, 14.02it/s]\n",
      "100%|██████████| 40/40 [00:02<00:00, 13.87it/s]\n",
      "100%|██████████| 40/40 [00:02<00:00, 14.03it/s]\n",
      "100%|██████████| 40/40 [00:02<00:00, 14.15it/s]\n",
      "100%|██████████| 40/40 [00:02<00:00, 13.56it/s]\n",
      "100%|██████████| 40/40 [00:02<00:00, 14.08it/s]\n",
      "100%|██████████| 40/40 [00:02<00:00, 14.37it/s]\n",
      "100%|██████████| 40/40 [00:02<00:00, 14.52it/s]\n",
      "100%|██████████| 40/40 [00:02<00:00, 13.80it/s]\n",
      "100%|██████████| 40/40 [00:02<00:00, 13.70it/s]\n",
      "100%|██████████| 40/40 [00:02<00:00, 14.29it/s]\n",
      "100%|██████████| 40/40 [00:02<00:00, 14.17it/s]\n",
      "100%|██████████| 40/40 [00:02<00:00, 13.65it/s]\n",
      "100%|██████████| 40/40 [00:02<00:00, 14.29it/s]\n",
      "100%|██████████| 40/40 [00:02<00:00, 13.95it/s]\n",
      "100%|██████████| 40/40 [00:02<00:00, 13.93it/s]\n",
      "100%|██████████| 40/40 [00:02<00:00, 13.84it/s]\n",
      "100%|██████████| 40/40 [00:02<00:00, 13.92it/s]\n",
      "100%|██████████| 40/40 [00:02<00:00, 13.85it/s]\n",
      "100%|██████████| 40/40 [00:02<00:00, 14.24it/s]\n",
      "100%|██████████| 40/40 [00:02<00:00, 14.11it/s]\n",
      "100%|██████████| 40/40 [00:02<00:00, 14.01it/s]\n",
      "100%|██████████| 40/40 [00:02<00:00, 13.57it/s]\n",
      "100%|██████████| 40/40 [00:02<00:00, 14.14it/s]\n"
     ]
    }
   ],
   "source": [
    "learnable_robot_unconstr = DifferentiableRobotModel(**learnable_robot_no_constraints_cfg.model)\n",
    "train_loss_unconstr, test_loss_unconstr = train_robot_model(learnable_robot_unconstr, train_data, test_data, lr=1e-2, n_epochs=50)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Unknown tag: material\n",
      "Unknown tag: self_collision_checking\n",
      "Unknown tag: material\n",
      "Unknown tag: material\n",
      "Unknown tag: material\n",
      "Unknown tag: material\n",
      "Unknown tag: material\n",
      "Unknown tag: material\n",
      "Unknown tag: material\n",
      "Unknown tag: hardwareInterface\n",
      "Unknown tag: robotNamespace\n",
      "Unknown tag: hardwareInterface\n",
      "Unknown tag: robotNamespace\n",
      "Unknown tag: hardwareInterface\n",
      "Unknown tag: robotNamespace\n",
      "Unknown tag: hardwareInterface\n",
      "Unknown tag: robotNamespace\n",
      "Unknown tag: hardwareInterface\n",
      "Unknown tag: robotNamespace\n",
      "Unknown tag: hardwareInterface\n",
      "Unknown tag: robotNamespace\n",
      "Unknown tag: hardwareInterface\n",
      "Unknown tag: robotNamespace\n",
      "100%|██████████| 40/40 [00:03<00:00, 11.31it/s]\n",
      "100%|██████████| 40/40 [00:03<00:00, 11.91it/s]\n",
      "100%|██████████| 40/40 [00:03<00:00, 11.70it/s]\n",
      "100%|██████████| 40/40 [00:03<00:00, 11.64it/s]\n",
      "100%|██████████| 40/40 [00:03<00:00, 11.43it/s]\n",
      "100%|██████████| 40/40 [00:03<00:00, 11.13it/s]\n",
      "100%|██████████| 40/40 [00:03<00:00, 11.32it/s]\n",
      "100%|██████████| 40/40 [00:03<00:00, 11.59it/s]\n",
      "100%|██████████| 40/40 [00:03<00:00, 11.15it/s]\n",
      "100%|██████████| 40/40 [00:03<00:00, 11.53it/s]\n",
      "100%|██████████| 40/40 [00:03<00:00, 11.10it/s]\n",
      "100%|██████████| 40/40 [00:03<00:00, 11.16it/s]\n",
      "100%|██████████| 40/40 [00:03<00:00, 11.05it/s]\n",
      "100%|██████████| 40/40 [00:03<00:00, 11.14it/s]\n",
      "100%|██████████| 40/40 [00:03<00:00, 11.23it/s]\n",
      "100%|██████████| 40/40 [00:03<00:00, 10.99it/s]\n",
      "100%|██████████| 40/40 [00:03<00:00, 10.96it/s]\n",
      "100%|██████████| 40/40 [00:03<00:00, 11.30it/s]\n",
      "100%|██████████| 40/40 [00:03<00:00, 11.47it/s]\n",
      "100%|██████████| 40/40 [00:03<00:00, 11.31it/s]\n",
      "100%|██████████| 40/40 [00:03<00:00, 11.00it/s]\n",
      "100%|██████████| 40/40 [00:03<00:00, 11.32it/s]\n",
      "100%|██████████| 40/40 [00:03<00:00, 11.29it/s]\n",
      "100%|██████████| 40/40 [00:03<00:00, 10.94it/s]\n",
      "100%|██████████| 40/40 [00:03<00:00, 10.71it/s]\n",
      "100%|██████████| 40/40 [00:03<00:00, 11.21it/s]\n",
      "100%|██████████| 40/40 [00:03<00:00, 10.87it/s]\n",
      "100%|██████████| 40/40 [00:03<00:00, 10.69it/s]\n",
      "100%|██████████| 40/40 [00:03<00:00, 11.04it/s]\n",
      "100%|██████████| 40/40 [00:03<00:00, 11.10it/s]\n",
      "100%|██████████| 40/40 [00:03<00:00, 11.08it/s]\n",
      "100%|██████████| 40/40 [00:03<00:00, 11.13it/s]\n",
      "100%|██████████| 40/40 [00:03<00:00, 10.81it/s]\n",
      "100%|██████████| 40/40 [00:03<00:00, 11.26it/s]\n",
      "100%|██████████| 40/40 [00:03<00:00, 11.10it/s]\n",
      "100%|██████████| 40/40 [00:03<00:00, 11.31it/s]\n",
      "100%|██████████| 40/40 [00:03<00:00, 11.06it/s]\n",
      "100%|██████████| 40/40 [00:03<00:00, 11.38it/s]\n",
      "100%|██████████| 40/40 [00:03<00:00, 11.00it/s]\n",
      "100%|██████████| 40/40 [00:03<00:00, 11.12it/s]\n",
      "100%|██████████| 40/40 [00:03<00:00, 10.84it/s]\n",
      "100%|██████████| 40/40 [00:03<00:00, 11.14it/s]\n",
      "100%|██████████| 40/40 [00:03<00:00, 11.07it/s]\n",
      "100%|██████████| 40/40 [00:03<00:00, 10.61it/s]\n",
      "100%|██████████| 40/40 [00:03<00:00, 11.18it/s]\n",
      "100%|██████████| 40/40 [00:03<00:00, 11.16it/s]\n",
      "100%|██████████| 40/40 [00:03<00:00, 11.21it/s]\n",
      "100%|██████████| 40/40 [00:03<00:00, 10.92it/s]\n",
      "100%|██████████| 40/40 [00:03<00:00, 10.95it/s]\n",
      "100%|██████████| 40/40 [00:03<00:00, 11.04it/s]\n"
     ]
    }
   ],
   "source": [
    "learnable_robot_constr = DifferentiableRobotModel(**learnable_robot_constrain_cfg.model)\n",
    "train_loss_constr, test_loss_constr = train_robot_model(learnable_robot_constr, train_data, test_data, lr=1e-2, n_epochs=50)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Results\n",
    "The learnable (structured) robot models outperform the simple mlp in generalizing to test data. The robot model with physical constraints on the parameters performs best."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<matplotlib.legend.Legend at 0x7fc8bb407cd0>"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAXwAAAD4CAYAAADvsV2wAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjMuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8vihELAAAACXBIWXMAAAsTAAALEwEAmpwYAABoC0lEQVR4nO2dd3iUVfbHP3cmyaR3em/SpBdBRIr0oqKuDV1AV6zYdl3LWnDXVRTXRQVFfopYEMsqKogNpIig9N4TQgktkN4zM/f3x8lMElIIqZPkfp7nfWbmvu2+k8k595577vcqrTUGg8FgqP1YqrsCBoPBYKgajME3GAyGOoIx+AaDwVBHMAbfYDAY6gjG4BsMBkMdwau6K1ASkZGRumXLltVdDYPBYKhRbN68+azWut755R5p8JVS44Hxbdu2ZdOmTdVdHYPBYKhRKKWOFFXukSEdrfUSrfXUkJCQ6q6KwWAw1Bo80uAbDAaDoeIxBt9gMBjqCMbgGwwGQx2hygZtlVLXAmOBYOA9rfVPVXVvg8FgMJSzha+Umq+UOqOU2nVe+Sil1H6l1CGl1BMAWuuvtdZ3AfcAN5XnvgaDwWC4eMob0lkAjMpfoJSyAnOA0UAn4BalVKd8hzydu99gMBgMVUi5DL7Weg0Qf15xX+CQ1jpaa50NfApco4SXge+11luKu6ZSaqpSapNSalNcXFw56lbmUw0Gg6FWUhmDtk2AY/k+H88tmwYMA25QSt1T3Mla63la695a69716hWaKFYqNm6Eu++GlJQynW4wGAy1kirL0tFav6G17qW1vkdrPbekY5VS45VS85KSksp0r3r14ORJ+OWXMp1uMBgMtZLKMPixQLN8n5vmllUZLVtC+/bwww8mtGMwGAwuKsPgbwTaKaVaKaV8gJuBby/mAhUhrTB6NBw/Drt3l/kSBoPBUKsob1rmImA90F4pdVwpdafW2g48APwI7AU+11pflNktb0gH4IorICBAWvkGg8FgAOXJi5j37t1bl0ctc/lyqF8funatwEoZDAaDh6OU2qy17n1+ucfLI5eHYcMqpj4Gg8FQG/BILZ2KlEc+dQo+/9wM3hoMBoNHGvyKiOG72LMHPvoIdu268LEGg8FQm/FIg1+RLXwzeGswGAyCRxr8imzh+/jA0KGwbh0kJ1dA5QwGg6GG4pEGv6KXOBw9Gux2WLGiQi5nMBgMNRKPNPgVTbNm0L07pKZWd00MBoOh+vDItMzK4J//BKWquxYGg8FQfXhkC78iY/h515TXciguGwwGQ43GIw1+RcfwXSxeDFOnipKmwWAw1DU80uBXFldeCTYb/Pe/4HRWd20MBoOhaqlTBj8iQlr4e/fCN99Ud20MBoOhaqlTBh9gyBC47DKZfXvs2IWPNxgMhtqCRxr8yhi0zbs2PPCAqGiaAVyDwVCXqNXyyCXhdILFI92dwWAwlI/i5JHrrMmzWERB85tvIDq6umtjMBgMlU+dNfgAaWnw5ZeStWO3V3dtDAaDoXKplQZ/wwb48MMLHxcYKPH8mBhYtKjSq2UwGAzVSpUZfKVUa6XUe0qp/1X2vebOhfvvh5SUCx/bty9cdRX873+wdCk4HJVdO4PBYKgeyruI+Xyl1Bml1K7zykcppfYrpQ4ppZ4A0FpHa63vLM/9Ssvdd4tQ2sKFpTv+rrugSxeYPx/OnavcuhkMBkN1Ud4W/gJgVP4CpZQVmAOMBjoBtyilOpXzPhdF376ijjl3bumWNgwIgH/9C15/XdI1Ab7+GuLjK7OWBoPBULWUy+BrrdcA55vFvsCh3BZ9NvApcE1pr6mUmqqU2qSU2hRXxkR5paSVv327xPNLe06zZvL+xAn44AO5xuLFkJ1dpmoYDAaDR1EZMfwmQP45rMeBJkqpCKXUXKCHUurJ4k7WWs8Dnge2+Pj4lLkSEyfKoOzcuRd/buPG8NZbcOmlEua55RaYPt20+A0GQ82mygZttdbntNb3aK3baK1fusCx5VbLDAoSo//ZZ5CQcPHnN2oEzz0HL74Io0ZBYiIEB8u+xYthzhz47TcpNxgMhppAZSyAEgs0y/e5aW5ZqVFKjQfGt23btlwVuftueOcd0c158MGyXaNLF9nyEx8Pq1fnLYzepAn07w+TJpWrugaDwVCpVIbB3wi0U0q1Qgz9zcCtlXCfC9KjhwzgvvMOTJtWcSte3XmnGPeoKNi9G3btgvyyP//4B9SrJyGhLl1kINistmUwGKqbcmnpKKUWAYOBSOA08JzW+j2l1BhgFmAF5mut/12W61eEls78+WKg16yBgQPLdalSkZMDr74qTiA5WcoiI2UcYMSIvKwh4wAMBkNlUZyWjkeKp+UL6dx18ODBcl0rLU1CLuPGwccfV0z9SoPWIr+8c6cY/4ED4fLLpey556Tl37VrXg/AYDAYKooaZfBdVJRa5rRpMG8exMZKa7s6OXJEZBx27szrAdSvL2Gg1q3FUZjWv8FgKA/FGfzKiOGXm4oatHVx990we7bk1v/1rxVyyTLTogU88YQY9iNHxPDv2CExfxAxt+XLpfXv6gFU8NK+BoOhjlInWvggIZXTp2HfPs/WwV+3Tgz+rl2QkSFl7drBf/4jLX+j428wGC5EjWrhVwZ33w233w4rV4pYmqdy+eWyORxw6JDMFk5JyQvzPPWUSDl37QrdukHHjlCO+WkGg6EO4ZEt/IoctHWRmSmDt1ddBZ9/XiGXrBY+/RS2bIEDB8QpeHvDhAnizMCMARgMhjo+aOvir3+FN96Q/PnmzSvsstVCRobMAdi2TUI+gwbJXIC775a4f48e0gNo3Ng4AIOhrmEMPnD0qBjH226D996rsMt6DHFxkgG0bVveAu316sGjj8okMIPBUDeo8zF8kFb9ffdJK/+xx6BDh+quUcVSr55ISGgNJ0+K4d++PS8DaMUKWLJEWv89ekj839u7WqtsMBiqEI9s4VdGDN9FXJzku48aBV98UaGX9nh+/10Wbd+7V+L/NpuEf556yhh+g6E2UaNa+FrrJcCS3r1731XR13aFOP75T9i8GXr1qug7eC79+smWkSH5/1u3igN0Gfu335a0T1f8PyCgeutrMBgqFo9s4buo6Bi+i+RkaeX36gU//ljhl6+x/Pe/sH69OASLRUI+w4d7dhqrwWAoTHEt/Do5hSc4GJ58En76CVatqu7aeA6PPAKffAIzZsANN0gq64kTsi87W8Y+fv1V1gs2GAw1jzrZwgdpxbZrJ8sarltnUheLw5XXHxMjTjI1VT537Cg9pKFDq1+fyGAwFKRGtfCVUuOVUvOS8ovMXyzRH0L0BxC7DM5thNQYsKe5d/v5wbPPykDmkiXlr3NtxeUIW7aEhQth5ky46SZp8X/0EZw7J/sPH84LBxkMBs+k9rbwNz8CaYfB6cgrq38FdHpc3juyyHHa6NQJfH0lhdFqLXeV6xQJCSLsZrHA//0ffPsteHlJzn/v3rL4TKNG1V1Lg6HuUTcnXmkN2QmQFQeZceATCqGXQsZJ2PwwNBzOVxuv5fpbI/noI5mQZSgbdruke27cCJs2ie5/SIj0ApSSsYD69cUhGAyGyqVuGvziyDwDhz+GM6vRKF79aDBfb76elX80M0JkFcTp0zL5q3t38bt33imL0fTsKS3/Xr3yFoU3GAwVizH4RZF5Bo5/zamtP/Lbb7Dk3ELefd8XL2sVKZAlH4To9+GS+8G/CWQnglcgWGpXM1hr2LBBto0bJRSklPSobryxhi/7qDVoO1jMzDWD51DtE6+UUgHAW0A2sEprvbCq7l0svvWh7VQaNr+JtGO7+eB1X9Iy4dOnnsPqFwENr4KQzhVvibLOweEP4dQv4BMiISf/JrDvNcg4BS1vhfpXgvLIMfWLRmWe5rLuXlzWOwht8SEqSgx/x46y/8gR+Ne/pOXft6+MAXjkzF9HNqQfhbQj0GCo/C4OzYMTy+Rzy1vkN2UweCjlXcR8PjAOOKO1vjRf+SjgdWQR83e11jOUUrcDiVrrJUqpz7TWN13o+pXewj+P116Dvz9m5+1H3uKO8b9i1Zng1xCCLoEGQyCit/zTJ+2W8QBnthjojJNQ73IIaA7nNonhDmwNQW0hsC0EtQHfhmIgjv4PjnwK2gFNr4XmfwIvf6nAuY1w+CNIPSzXankbRPYrncM5uwHOrIbUKHDmSKvTJwx6zZL9O5+HnBQI7gAhHSG4I9jCi79e/parIxtOr4Css+KsLN7iCEO7Fn0NrSEtBgJbyeddL8DZP+S91Ud6MX6NoftLAMStf4fDW3eTEJ8DTjs27xy8QprT4cZ/Sspn+nF5Fq9qmPqbcRJOr4b4TZByELRTyi+bB36NIGEHxK2FU8sBJzQcAc1vBN8y5qpqJ6Dkb551TsaeHOmgvOTvVtaeRHYSnFkl9fPyg6x4UFZpcBSHPR0Sd0DGCQhqB0Ht5e93MWSegbPr5Tfd4WEpi98s/xcl3bui0Fqy87wDK/9eHkRltfAXALOBD/PdyArMAYYDx4GNSqlvgabAztzDHHggjz4KNpsXUx94kCV7p/LF2+uxJa6Wf/TQLnJQ5mnY8Wzhk33riZEOaAGR/SE1Go5/A0677O/xMoR0krBNeG9oPVmcSX4i+si+uN8g5mPY/SJ0fFScTcZpOPc7+DeX++SkQMI2aHqNGIeEzZC4XQyx1VcMhE9o3rXD+4hDOLFM6gUQeRlc+rS83/WC1M2eKtd2pEGjUdDuHulpHHwbUGLg7Rlw4gdodTu0uFEMw7kN4BMudYxbJ8aq33z5XlrdLs/lurY9BSx5hqNew0DqXVkfu9ObE6e8OHLUi/0x4fTO/R+N/elFVMYxgus3IKB+c5QtQr7LBkPkgIyT8sw5qZCTJJutPgS3k3/4mE/AO1gMjHewbLZ64B0kWVzZ58QBa6e8psWIgfNrJK35I5+I029+gzjygFbg20DuHdZVthY3wdHP4eRP4ijbP1j4N6Kd4owt3vKdpkRBwhZIPyFGNSNWDPPAL+R5ji3O+1uBlIX3hI6PlS7s53TI9U8th3N/yGdbPWmcHPsKYr+F0G7Sm6x3eZ5DdWTBzumQvLdgllvLidDyZvn7J+2SxoPFW57JmSPfp8VbfkenlsvvICVXCyuwlfxOlBX2vCLfUaPR0Oy6khsepcFpz/s+kvZKgyz9OKQfk80nHPrOlf27XpDjw7rLFtCihsYSy0a5Y/hKqZbAUlcLXynVH5iutR6Z+/nJ3EOPAwla66VKqU+11jcXc72pwFSA5s2b9zpy5Ei56lcW3n0Xpk6FIUMk1bCApowjS4x5doIYVb9G0o232gpfyGkXg5FyCBoMkn/Y0q5Q4nTA2d/kn8q3vrQy975a+Ljeb0JgS/kntPpe+NpOu9Q/ea+0tBvm6ia4nJhXYN4W0lGcEEiL0CdUDJV2ilH0CpaW7LmNsPOfcpzFG8J7Qb0B0jux+l74WYsg/9f0yZwtHN0dRcPAaJpFxNK84TkCmvWi8dBH5cC1N4Ijs+AFmoyBdvdK6+63W/IGCly0uBlaTYTMs/D7lMIVaD0Zml8vvRtHRulbo5lnxKjZIiB5v3wvzmzQOXnGs89bENBMjPmhd8Xg+TWW35ItUoyg1RfSjkqvyuovTvLcRsiOz3PS0R/Ib9Bqy3NW/k2h/kDISYZN03L/biEScmo4TBoLAGnHpMV/Zo30Ui1e4kBdjmr3S1Kn8F7g3wxSDsi1/RpJC33H9MLP3u3f4vzO/CpGPaidOJJ6A+Q8F+mx4hxPr5LvqlFur8gWDom7pTeVdU4ccU6ynNP9FemZxH4ndVZK9mXFy/c78Esp2/e6OBtbuNTbv5kY9caj5DpR78n3mB4rn31CoMUt0GSs/H12PZ/7W8ntaXkFSP3rXyn/Oyd/kt6C8pbvTHlJONa3vvxWUqPAmSV2wpktW9Al8vfOSZbvxtWDQ8nzh14q309OKqTsl2v7NZLGUhmptEHbIgz+DcAorfVfcj/fDlwGPI70BjKBtSXF8Mutlhm/Vb7cBoMu/txcPvoIJk+W5QaXLIHQ0DJfquLIThIHkn4ULDb5Zyxv66gicDmAzLPSE/Lyq/BbxMfnDfxu3w5dumimT1egNXvX/ErzRikEhAaBd4hstghpcbrqZ0+V7y8nWXoA/k3EEDiyIO5XwCL/fMoi/7xB7co/hnJ6NSTvyTUOPuIMLd7QcLgYGnvuLLWyfF9aw/YnxUDmp97l0Dm3jRX1HoRcKr+T4noEWkuDJO5XcRhtS6FX6MiG5H1i3NB5xi+8jzQAsuKlBX+h8YyMU3D0Czj9C/R8TXoBx76SsKZPeO7fMETu0fExcWwnfsitqzO31xYmxza7TuqQFS/HXSj8l3lWesQJ28UJNr9BDP62x3P/7krua0+Tv1eza6WRt+7Pha/VepKcn3ES/phaeH+7e8ShpEbDpocK73f14hN3wbbcv52rN1VGqt3ga60fuNhrlzmGv+Y6OL5YYuA9ZhYOnZSSzz6DiRMhMFB09B98EBqW7VKGCiQrS1b3ql8fEhPhz7n/gx06yKBvnz6y9kGd6Kk7suVVuRxWDXzonGQx3iCtaE99Dte8Hntabo/NLk7SVk8cnWt8z+IjTsdik/c+IdJjc9pzZ/vr3LGa3FfvINlvT5denc6RkKRfgzJXtSoNfpEhHa31SxdxzfK18O3pEv/eO1O+yK7/gnb3lSndcfNmERP78kvJHJk0SZZKbN/+4qtlqHi0liUrXSmfhw5J+YMPitJndq49NPMrDHWJqjT4XsAB4CogFtgI3Kq13l3sRQpfs2IWQEk+IHHMUz/J4FSft6TLWwYOHYL//Afef1+MyDXXwP33y1qyHplCWEc5d05m+vbuDRER8PPPMG+e6Pv37Svl4R4QBTMYKpNKMfhKqUXAYCASOA08p7V+Tyk1BpiFpGXO11r/uyzXr5C0TK0lLrjlYRm5bz0Fev63zClhp0/D7NkwZ45MIAoLg3HjYMIEGDHCLBriaRw8CMuXS+vftc5vmzbw8suy4pfBUBupUTNtK6KF/+TyJ/Hz9qNNWBvahrelbXBDwqPeQu37j6Q2DvgUIvuWuY7p6bJ4ytdfy6BuQoKIsI0YIa3/MWNMvN+T0FomeG3aBMePw8MPS/kbb4gOUO/estJXUFC1VtNgqBBqlMF3UdYW/mM/PcZX+75iROsRxKbEopFnHNVmFPe36g6/3Sp5z91fgg6PljsbIydHFgb5+mvZjh2T8l69YOxYMf59+oiqpMGzePttWLtWVkFTSgZ+R440q3wZajY1yuCXt4W/aOcibv3qVv7W/2/8a+i/OJxwmEPxh2gc1JhejXvJSPvvd0omT6NR0P+DCpsSr7WkDS5bBt99J3r7TqespTtqlDiAESMkFGTwDJxOGaPZtEm2nj1F5yc7G956S+L/PXuK+qfBUBOoUQbfRXli+Pd/dz9vbXqLxTct5toO17rLo+Kj+Hz35zza7xFsMe+Lbr4tHPp/DA2HVlDN8zh3TkI/330HP/wg+eRWq+T3jxkjDuDSSz0zC62u4pr0deQIPPWUtP4B2raVsM+IESZcZ/Bs6pzBz7JnccX7V3Dw3EE2T91Mm/A2AOw+s5snVjzBHd3vYELHCaKF8ttNMpEktAvUHwT1B8vMunLMdCsKhwP++ENa/8uWwdatUt60qRj/MWMklBBYt2Q/PBqtpfW/ZYuk6O7fD6+8Imm5+/fDgQMiAd20qXHaBs+hRhn8ikrLjEmMoec7PWkZ2pJ1d67D10um+T/zyzNEJ0bz7vh38fP2k8kQB2bDqRWiY+NIlwuEdBYHEN5LtFuCO1ao4NOJE/D999L6X74cUlIkX3zQoDwH0K6dMSSeRHq6DM5bLLLg+6JFUh4eLqGfbt3k72cWejFUJzXK4LuoiLTMpQeWMn7ReKb2nMo7498BYP/Z/fzt579xe9fbubHzjQVPcGSLVsiZ1bLFrZVp+S78Guca/04yNd8nTDZbeN573walVzVMPQwb7ian1T38evg6d+t/717Z3aZNnvEfPFiMjcFzOH1aemrbt8OOHeIIPvxQnPTKlTJHo0sXE/83VC111uCDpGjO+G0GH1z7AX/uJvPw/7n6n+w9u5d3x79LgE8JyfPOXPXEpD0iOJa0J/f9ngKLohfAvykMXQ7BF5iOm3UOfh4gIlsAXV+Azk+BUhw+LIb/++/hl19kcXA/Pwn5jBkDo0fLwuIGz0FrGbOJzFVGfuABGQcAaNZMDP9ll8kAsMFQmdQog19hM21zsTvtDPtwGBtiN7Dhrg1cWv9SohOi2XpyK+MuGYfNqwwzcLQWEa7shHxbvKgb7pwOWGDYquKNvj0DfrkK4rfA4KUQvQBiFopo0mXvFlCZzMiA1avzMn+io6W8U6e8gd8BA8yMX0/DbhfZh507Ydcu2L1bwj0PPCA/n3fekZBd587QoIEJ3Rkqjhpl8F1U5AIoJ1NO0uOdHoT6hrLl7i34e/tXyHWLJGkPrBhCsUbf6YC1N4g87hVfiAyv1rDnJdj+D4joB1d+XaR4ktYyWPj99+IAVq+WeQDBwaIdM3astP5NFonn4XCI8w4MhLNnxfCn5XYSw8PFgV99dd5KYAZDWanzBh9gefRyhn80nGevfJbnhzwPwJoja0jKTGJ8+/EVdh+geKOvNWx6AA6+Bb3egPbTCp537CtYd7tIww5aAmHdSrxNSgqsWJGX+RObK/Pdq1de7L9PH0kFNXgWWsPRo9Ly37NHXu+9VzR/9u4V3aYOHfI2owFkKC3G4Ody65e38tXer9h9327ahLdh5m8z+SP2D969+l1CfUMr9F5FGv3dM0THvONj0OOVos+L3wKrr4acRLjiS2g8slS301oGDl2hn/XrZVJRZCSMG5PFyNE2Ro40k748GdccgB07ZE2GQ4ckNARi8F98EZo0EVloq9VIQRiKxhj8XGKTY+kwpwODWw5myS1LiE2O5d7v7uXq9lfzl55/qdB7AQWN/iX3w45noMWtcPlHJUs6pJ+AVWNkibhhq2U93YskPh5+/iGTiCN/ZVDTedw57z0+Wf9nLr9cQj/jxkkYwcSOPZecHBmz2b9fjP8DD0jq7vz5sHixhO5at5ZsrlatRBPI/D0NNcrgV/Sg7fnM/G0mf1/+d5bcsoRxl4xj1u+z+PXor/zf+P8j3K8S+s0uo595RpaaG/x96RaDzjgFP/WT5ftG/C5LGV7UfffCbzdD4g50YFtIjeJ/xz/ixUUT2bZNDmnRQgz/2LGypKNJ+6wZREXBtm3iBKKi4ORJae0vXCgGf/Fi6QW0aCHZXE2bmjUB6hI1yuC7qIwWPkC2I5tuc7uR7chm9327SchIYOrSqUzsMpGbLy37smIlkrQXoudD56cvbvJW0h74aYCs2jVineT5XwitJetn0wPg5Q/9P5QJZKvGQtwa6L+QWO+bWbYMli6VSV/p6eDvD8OGiQMYM0ZCB4aaQXo6nDmTl6r78sui4+QKB1ksMjbwj3/I5+3bZW5A48bGEdRGjME/jxXRKxj20TD+OfifPDPoGd7d8i6d6nXi8mZlWyClUjm9GlaOkEXBh/xU9ILpLnKSYcO9cOQT6U30/wj8G8s+e5qEieJ+gwGLoPmfAMjMhFWrxPgvXZqXO96jB4wfL1vPnkbts6bhcMhs7iNHZAsMFOlugFtvlQF/pSQs1LSppPa6VELj42Wsx4SHaibG4BfBjV/cyJIDS9h7/15ahrastPtUCDGfwLqJ0OJmuHxh4fh/TgqcWCZpnWkx0OV56PQEWM5Lz8lJhVWj4OzvcMXnsvhzPrSWjJElS8T4uwZ+GzWSlv+4cdIL8K/ErNZiyU4Up+VfA7oeOaki19F6Evg1qu7aFEBriIkRGe9jx2R9gGPHZFLY7bfLmsE33CALxDRqJL2Ahg2hXz9JGdVanImRj/BcjMEvgmNJx+gwpwMj2oxg8U2LSclKISohiu4Nu1faPcuFK8On0xOi5Z+dAMe/hWNfwsmfwJkFAS2lVV//iuKvk5MCK0fCuY0w8H/Q9JpiDz17VnL+lywRtc+UFInzDxsmOePjxolRQDtlrMGrEjxBxmnY95qksioLjN4Gga0q/j4VReZZWD0Wzm2QAfoBC6u7RhdFZqbM7j5xIm87cwamTJHe3okTcM89soRkvXqy1a8vk8patpSB5uxsaRSYHkL1UO0GXynVGvgHEKK1vqE051S2wQeYsXYGT654ku8nfk9UfBQrY1by0YSPRFTN09AaNt4Lh96BiMtE80fbwb8ZNLtetsj+hVv1RZGdJGGihK3Q4z+5i7yXfF52NqxZIy3/b7+Fw4fB35bGs7d9wF+ueJ1wn2i45D5Ul2dlHkF5STsKe16B6PfAmQ3N/gQnf5D01uFrS69XVBEcnAvRH0CPmSU709QYcabpR6HelXDqZxizE0I7V1lVK4P8rfr4eJH8PnVKlo2Mi5OGwWOPiez3tm3wzDPSQ4iMFMcQESG9hubNZXW4kyclZBQWZhIFKoNyGXyl1HxgHHDGtVh5bvko4HVk7dp3tdYzSnGt/3mSwc92ZNPl7S44tZMv//Ql/1j5Dx7s+yDD2wyv1PuWGacdfrsFEndAswli5MPLmIuXnQhrbxSjFNZDFnmP7FeqU3Xacc6um03gqXn4WRPYENWHPbGduP2Kj8hyBnM89Flajbwfb1sxI4JpR+W+9gwZk7D6giX3VXnBsf/B4Y/kuVr9GTo+DsHt4Oj/YO2fSp7HUNGkRMGyS8GZA9oBbe+B7jMKD74n7JBwmT1D5DKCO8A3raDRCOlJ1WKcTnEKVqs4gvXrRVco//bEEyIlsWIFzJqVd66vrxj+556TRIE9e0SQLiQEQkNlFnlwsIwzmDBS6Sivwb8SSAU+dBl8pZQVOAAMB44DG4FbEOP/0nmXuENrfSb3PI8y+AA/HPqB0QtHM//q+fwR+wchthBeHv5ypd/XI9Aajn4OWx6VZR/b/AW6vQS+kYWPzYqX2H/Mx3D0C8AJTSdAh0c47bicpd8ptq7azTXN/srwS38k6kxbvj4ykyZ9r2H0KDshOesh9js48R0k7S65XlZfaHOXGPaAZgX3bbgXDs2V9NbGoyrsqygSraUndG4DjNwoLf0Dr4siau/ZeWMgp1fDmmvAKxCG/JjXot/xLOz6F4zeCmHdK7eupSHjJHgFgHdwtVUhMVF6h/Hx0tp3bVOnioFfvFhmGZ9vmj76SPb/738SXgwOloHooCB5vesucQgHD4qDCQyEgIC812oZd6omyh3SUUq1BJbmM/j9gela65G5n58E0Fqfb+zPv47HGXytNW3eaEO7iHbc1fMuPtj+Ae+Me4fGQY0r/d4eQ04K7Hwe9s8C7xAZIwjvLQb+7O9w7g9IOSDHegeLY7jkgSJj6enpsOOH72l+7q80DtjL9iNdaVHvCKH+STi0FzlhV+Lbagw0Hg22ejL24MgER1be+6BLinY6IC3ony6TeQpjtpd+UFRr6Rmd+A5OLYeWt0ObKSWfc/gjWP9n6D0HLrlPys5tgg13QcI2aHotNB4rKbCBrWHIDxDQPO/87ERp5dcfCIO+LV09z+fs73D4Y+j5agFRvYvGkQ3ftpZQ2FW/ePQ4iNMp40UJCfKanCyDxlYr/PabpJympEBqqrympYlDUArefBN++qng9Ww2cRQA774raakuJxAQIKGnSZNk/6ZNkJQk6rT+/vIaGJiXpux0en7GWnEGvzwdpCbAsXyfjwOXlVCBCODfQA+l1JPFOQal1FRgKkDz5s2LOqTCUUpx86U388pvr/DGqDewKAs7Tu+oWwbfO0gMSuvJsOl+2HB33j7f+jI20HqKhHwi+kgrsRj8/aHfdaPBORzngXm08XmPvSev44MlY/nw5+GkZATTqxdce61snTtfZETKyw8GfAY/9IZ1t0mqanHjD/Y0WdjmxHeSxZR+PPeZGsAfd4J3oDs9tRCZZ6XnE9kf2t2TVx7RG0ZugH2zYOdzcPxrEbwbvLTw2IVPKHT8G+x4Gs7+AZHF/osUjdaw8X5I2CLqrP0/LPtI6PGvISNWQmfLr4Shv0iYzAOxWCSkU9Q6AgMGyFYct90m80jS0sQhpKXJQLKLyEhRJ01Lk57A0aPSW3CxeLFIW+SnRQuYPVve//3vMtnNz0/CUb6+onX04IOy//3385IbXFvTpjK+AXkr3fn6iiOy2cShuJ7VJa9RGZSnhX8DMEpr/Zfcz7cDl2mtHyh3pSp5pm1R7Di9g25zu/HWmLe46dKbKmfGbU1Ba4hdKgu/RPaXhV4q4BeoNezbB998A19/Lcs9gsgCuIx///4XIfQWNV+MdtcX4NJ/5JU7suDE9yI3fWJpbvZQEDQaLq3xxqOlF7NypPRcBn0n+85n/WS5xuitEHpp4f0AqdFw7GtxCMVlKOWkSMs6rCcM/bGUD5fLyZ+knvUGyPyJ7jOg0+MXdw0XywfL2MnALyVMpbzgqhWyoI/BTWqqbOnpom6akSGhou7dZf8PP8jCNxkZktGUkSGpq64ewtNPS5prZqZsTqekvD79tOy//XYJa+Xnyitl0Bvgxhvhuuvg5nLMAfWYkE4p71XlBl9rzaVvX0qEXwRrpqxxlymTV1ZpnDwp2T5ffy0DeTk5kuJ39dVi/IcNu0AGh9YyN+Ho53DVSkkNjVkoA77ZCRIuanGThF3qDSwsZ5GdCMsHQWqUtHYj++btO7UCfhkmC9J0+3f5H3bvq7D1MRi2RsI7pWXFUFkg5+po+H0yHPkMrvwGml6kumvSHviuc57DSNoDK66SQeihyyGs68Vdz1AqtJbZzg5H3m85JkacRFaWOITsbMli6pw77LNokbzvWo4/SXEGH611qTagJbAr32cvIBpoBfgA24HOpb1eabZevXrpquSfq/6pmY4+lnRMv/776/q1da9V6f3rMklJWn/6qdY336x1UJDWoHVAgNY33KD1woVaJyQUc2J2ktbftNZ6IbJ9FqD1b7dpHfu91o6cC984/YSc/0W41ol7pCwnXetv2sqWk14xD5iTpvWXDbX+eZDWTmfpzon7XZ5pz3/yrvF9L60/C9Q6YdfF3X/jNK0X+WidcSavLGm/1l81kWc/t+nirmfwaIBNugibWqqhB6XUImA90F4pdVwpdafW2g48APwI7AU+11pfIPWidCilxiul5iUlJVXE5UqNS0fns12fYbPa+PXor6Rmp17gLENFEBwMN90krZu4OOk233YbrF0LEyfKxJ6RI2HuXOkZuPEOhoFfyUphly+E606LEmnjUWApxRCVX6PcMQBv+GU4pB2B3S9A6iHoO1fGCyoCL3/pLZxZDad/Kd05e14S7aS2d+Vd48pvJBNozdWyRGZpsKfB4Q9krMK3Xl558CUwfI18hyuGwsmfZXGeyuL3O+Q7PvVL4RQcQ5VQp2faFnnPeb1RSvHp9Z/y8I8Pc2/vexnTbkyV1sGQh9Mpsf6vv5bBNFeEr18/mDBBtnYVMe6YsF3CO7YIiXO3nAj9F1TAhfPhyIQl7WSi3PDfSh4XcYVgLn0Wuj5fcN/ZP6Sukf1h6E8XnoB26P9gw1SZrFaviNHOtKMS3kk9JGMdkf3luHpXyCBzCQP0pSZ+C/zQSwaMnVkQeTlc+gw0Gmmm41YC1T7T9mKojhi+i/+s+w9/+/lv7L9/P29ueBNvqzevjXytSutgKBqXzs/ixeIANm+W8s6dJeY/YYKIvJXZfpz5VQYzvQJg7L7i00LLw8F3YOM9cOW3Jcfh10+SSWbXHCm6Hoc/hvW3yySwvm8Xfx2txdBqO4zeXvyXk50ocyTO/gZxayFxF6BBWWVQ11Zfehu2cPDJ3QKaQ7MbSjeze+3NkiU1/qBIgeyZAenHILyPGP4m44zhr0BqlMF3UR0t/GNJx2g+qzn/GvIvutTvwrtb32XOmDk0D6maFFFD6Tl6NK/lv2aN9AaaN88z/ldcUYaZmQnbRa8ntEsl1BjJhf++q0yAGrysmBb3Efi2rSyY02tW8dfa9gTseRl6vgYdHin6mLN/yJoKfd4umFp6IbITJf8/7jdI3C7ho+x4GQzPOicOBGSSXucnSr5WarT0bDr8NW92tCMbDn8oYavUaJnz0XcuhPcqfR0NxVLuQduq3IDxwLy2bdtW9FhGqRg4f6DuNKeTTkhP0At3LNTx6fHVUg9D6YmL0/r997W++mqtfX1l0DciQuvJk7X+5hut0yto7LVCSD2q9bfttP7UX+uTywvv3zhN60+85LiScNi1XnO91guV1ke+KPqYdX+WQd7s5PLX24XTqXV2itarxsu100+WfPyG+7Re5K11WmzhfY4craMWyID2JxatNz+qdU5qxdW1IrBnyID+sW9kAH3TI1qnxlR3rUqEYgZtTQu/CN7a+Bb3L7ufHffsoEuDSmrpGSqN1FQR91q8WITekpJkMtioUdLyHzvWA9b1zTglA5gpB+GKL/LCO5lx8E0LSSft9/6Fr2PPgJXDZfbv0OUFhd2yzsHiJtDmDtFJqmiSD8KyzjJjud97RR+TGQffNBfV0OKOAelRbHtChAEDWkCfuZUvm1Ec9nSRIz/6GSQfkNAT59nJJuPLPnO6Ciiuhe/hE4Srhxs63YBVWfl016eATMqas2EOnuwcDXkEBsL118PHH4us708/weTJMh3/9tsl42f4cJgzR7TgqwW/hrKwfWgX+PU6OPK5lO9/QwZ3O5ZycpWXn2TuBLaUzJ2kfXn7ohfIAGm7eyu48rkEt4NLHoTo92VQtigOvCkT4To+VvK1fEIlpDNsDVj9YNVo+G2iLAvqyJQQWNIeCTHFLpXvKzOuYp8nNQa2/h2+birSGenHof6V0OU56P+xLDN6/Tno9iLELhH9pBqGR7bwq3PQ1sXIj0dyKP4Qh6Yd4vtD3/P2prf5x8B/0K9p6dQkDZ6H0yk6KYsXy7Z/v5T37i0rQZVJ5qG8ZCfB6nFwdh30fF0WuW84VGbDXgyphyVWb/UTw+RbH5ZcImmnw3+tnLqD1H9JO1EGHba64JeXkyqt+/qD4MrFpb+mI0sGdXe/KAql57euXVh8oPmNIu0d2a9sfzitJU32wJtixFEiCNh+mkzWK+qa9gxYegn4NoSRfxRejMgDMIO2F8mCbQuY8s0U/vjLH/Rq1Itp30/D7rQzZ8wcvK1VqMNuqDTyyzz8/ruUtW4thv/qq0WvpUrkeO1psOZaEXQD0emJ6HPx1zm3CVYMhqD2IjXx6/UyN6HlrRVZ28IcmifaS1d8Ac3z6SLuex22PAzD10G9/hd/3aS9MnPay18yhLzD5NW1rnPMR7JGgT1F5L3b3Qctbyk5jdTpEAG9M6vhzBqI+xWyzoItEtpOlayn89VZiyJ6Afw+BS5fBC0raR3scmAM/kWSmJlIg1cbcF/v+/jvqP+y9eRWnl31LFO6T+G6jtdd+AKGGsXJk7Kq1+LFstqTa7r72LHS+h8xQkJFlYYjE/64S9Igy5P/H7tMQjsoMYzXHit5DeSKwOmAH3rKesrj9oqipzNHMo0CWsjkrsoiJ1WcwsE5kLhTNJJCOouTsPpLj8cr9zXtiKSc5uRO6AxoBQ0GQcPhInN9MUqkBZ55X9m+45Qo0XwKbi/S2fknxZWTGmXwPSGkAzDhswn8cfwPjj1yDKvFyj9X/5NdZ3bxf+P/jxDfImT8DLWClBQZ9P3mG/juO5HotdlkgW/Xso4uqVyPxDXRqtOT0P3Fqrnn6ZUyW7fbi9D5ybx5AoOWQpOxlX9/rSUsFvUupB0DRwY40mUA1pEhvSjf+hKTr3elvJamJV8SLmG7Hv+Bjo9e3LlHv5Qegj0lr8yvsfRUwrrL1mCozHsoAzXK4LuozhY+iMTCzV/ezMpJKxnccjCxybFsP72dkW1GYi3NZBNDjcduF3mHb74RobfoaCnv2VOM//jx0KOHB84Zis9V+KzKZSDXTJBVzMYdkJW/tIYxOzzwy6lAfhkJ8Rvh6qi8UFNJOHNg6+Ow/78Q0Rcue1dCSvFbZX2FhK2QvFdE7YatFsdUBozBLwPpOelEvhLJXT3v4vXRr1dbPQyegdawd68Y/iVLZBk/raW1P3astPyvuqpuraxUgJQo+K6jDOAm7oR+H0DrP1d3rSqXhO3wfQ/o+FdZ77gk0mNlSdGz62TxoB7/KazgChLeS9oNwR2Ll9y+ACYtswz4e/szoPkAVh1ZVaB85eGVvLz2ZZOmWcdQCjp1krVZf/tN1m59/33ROv/kE2nxh4fL4htz5ogMbp0iqA20f1iMvX8zGUCt7YR1kzWX978haZ3FcWq5OIbE7TLQ2/vNoo09yFhCeK8yG/uSMAb/AgxpOYQdp3dwNv2suyzDnsHaY2v5/fjv1VgzQ3VTv77k93/5payc9PPPcO+9IvD2wAPQqpWkef7tb3kDwbWeS5+GkEuhy/SqDSdVJ91ekNTMHU/nlWktWUZR78G6P8MvI2RQduTGas3q8ciQjqcM2gKsO7aOAfMH8L8//Y/rO10PgMPp4KEfHiIxM5GZw2fSKKiUa6oa6gwHDsgs3++/h9WrZXGXwEAJ+YweLVsVreBpqAq2PSW6QJ0eh8TdErbJjpd9tggRmevxqiypWQWYGH4ZyXHkEPZyGJO7T2b2mNnu8tjkWB77+TECfQKZOXymydoxFEtqqrTwv/8eli0T0TeQdVBHjpSUz8GD63DsvzaQnQRL20PmaYm917tcJKDrDYCgS6p84NoY/HIw6uNRHEs+xu77Cq7vsv/sfp765Smm9pzKyLYjq6l2hpqEa+D3xx9lW71alrnz8YGBA8UBDBsG3brJQt6GGkTmWTHs5y9kXw0Yg18OXl77Mk+seIJTfz1Fg8AGBfbFpcVRL6DiJkwY6hYZGfDrr3kOYHdumyIiQsI/w4bJ1qpV9dbTULMwWTrlYEirIQCsillVaJ/L2O8/u58F2xaYzB3DReHnJyGd//wHdu2C2Fj48ENJ81y7FqZOFbmH1q3hzjtFEC42trprbaipVKnBV0pdq5T6P6XUZ0qpEVV57/LQs1FPgnyCWBmzsthjNp3YxJd7v+Tz3Z9XYc0MtY3GjUXR84MPRMlzzx544w3o2hW++kr2NW0Kl1wCd98Nn34KJ05Ud60NNYVSS0MppeYD44AzWutL85WPAl4HrMC7WusZxV1Da/018LVSKgx4FfipjPWuUrwsXlzZ4soSDf6tXW7ldNppPt75MRH+EQxrPawKa2iojSgFHTvKNm0aOBywfTusXCnbokUwb54c27atjAFceaVsrVrV7gmuhrJxMVqAC4DZwIeuAqWUFZgDDAeOAxuVUt8ixv+l886/Q2t9Jvf907nn1RiGtBzCdwe/40TKCRoHNS60XynFg5c9SEJGAm/88QZO7WREmxrTiTHUAKxWkXTo2RP++leRfdi6VcYA1qwR+Yf3c9dMadJE1D4vv1y27t3Bu46kxRuK56IGbZVSLYGlrha+Uqo/MF1rPTL385MAWuvzjb3rfAXMAH7WWi+/0P2KGrTNycnh+PHjZGZmlrreFUG2I5uTKSeJ9I8kwKd4+VWtNUlZSSiUSdU0uPH19aVp06Z4V6LVdTolA2jNGtnWrctLAfXzg759xfj36yezgxs0KPl6hppLcYO25VX7bgIcy/f5OHBZCcdPA4YBIUqptlrruUVUdCowFaB5ETNTjh8/TlBQEC1btkRVYZ9Va43zlJMwvzBahra84LEajUVZcDgdWJSlSutq8Cy01pw7d47jx4/TqhLTbSwWmdnbubPM+AUZB1i/XqQg1q2DmTOlZwAy8euyy8QRXHaZ9BwCSpCSN9R8qmJ5Bzda6zeANy5wzDyl1ElgvI+PT6El7DMzM6vc2IOEbIJsQaRkpZTqWIXC6XRyPPk4vl6+1A+ob4x+HUUpRUREBHFxFbwkXylo2hT+9CfZANLTJQz0xx+wYYO8fvGF7LNYZDJY797Qq5ds3bsbJ1CbKK/BjwXyi0o3zS2rVKrLcAb5BJGYmUiWPQub14UXPFBKEegTSHxGPE7tpEFgAyweuByaofLxFGfv7y+x/QED8srOnBHjv3EjbN4sawB/mDtS53IC3bvLZLBu3eS9CQfVTMpr8DcC7ZRSrRBDfzNQ7vXUtNZLgCW9e/e+q7zXqkiCbEEApGSnlNrgR/hHYFVW4tLjcKY4aRjY0GjpGzyK+vVF2nncuLyyEyfE+G/eDFu2yJyATz7J29+ggRj/Ll3g0ktl69TJyEN4OqVubiqlFgHrgfZKqeNKqTu11nbgAeBHYC/wudZ6d0nXKeW9xiul5iUlJZX3UhWKn5cfXhavUoV1Vq1axbjc/6BQv1AaBDYgw57BmbQzxZ7z4otVszrR4MGDudAM5tIc40nExMTwSX6LdBGU9Xv/y1/+wp49e8p0rqfTuLEs7jJ9uuj/HzkiiqArV8J//yvib2fOwOzZMGUK9Okj4nBt28qawE89BR99JIvGp6ZW99MYXJS6ha+1LlLcWmu9DFhWYTXCc1v4rhBNSnaKe0at1hpLKURPgm3B2Kw2d0jH4XQAFGjtv/jiizz11FOVUPPaj8vg33pr4Q6m3W7Hq4TVyIv73rXWJf5933333bJXuAYSHi4ib4MH55XZ7bIK2K5dedvOnbI0pGtwGGQsoWNHCQ+1aycTx9q1gxYtJN3UUDVU6aBtacknj1zicQ8/DNu2Vey9u3eHWbOK3594KpHrx1/PFf2vYNvWbSxbtozZs2fz/fffo5Ti6aef5qabbgIgOTmZsWPHcujQIYYMGcJbb72FxWJh0aJF/POFf+J0Ohk7diyvvfoaTzzxBBkZGXTv3p3OnTuzcOHCAvcNDAzk3nvvZdmyZTRq1IgXX3yRv//97xw9epRZs2Zx9dVXk5mZyb333sumTZvw8vLitddeY8iQIWRkZDBlyhS2b99Ohw4dyMjIcF/3p59+4rnnniMrK4s2bdrw/vvvE1jCat0tW7Zk0qRJLFmyhJycHL744gs6dOhAfHw8d9xxB9HR0fj7+zNv3jy6du1a4FyHw8Hjjz/ODz/8gMVi4a677mLatGmsWLGCv/3tb9jtdvr06cPbb7+NzWYr9l6rV6/moYceAsQJr1mzhieeeIK9e/fSvXt3Jk2aRFhYGF999RWpqak4HA5Wr15d5POc/73/+9//ZuTIkVx22WVs3ryZZcuWMWPGDDZu3EhGRgY33HADzz//PCC9oFdffZXevXsTGBjIQw89xNKlS/Hz8+Obb76hQR0IdHt5ifG+5BK47rq88pwciIqSNNG9e2HfPnldsEDWDHbh4yOyEe3aQZs28r5NG9latpS1hA0ViKsV44lbr1699Pns2bPH/f6hh7QeNKhit4ceKnTLAuw9sFcrpfT3K7/XWmv9v//9Tw8bNkzb7XZ96tQp3axZM33ixAm9cuVKbbPZdFRUlLbb7XrYsGH6iy++0LGxsbpZs2b6SOwRfSjukO53RT+9YNECbXfYdUBAQLH3BfSyZcu01lpfe+21evjw4To7O1tv27ZNd+vWTWut9auvvqqnTJki9dy7Vzdr1kxnZGTo//znP+7y7du3a6vVqjdu3Kjj4uL0wIEDdWpqqtZa6xkzZujnn39ea631oEGD9MaNGwvVo0WLFvqNN97QWms9Z84cfeedd2qttX7ggQf09OnTtdZar1ixwl2n/Lz11lv6+uuv1zk5OVprrc+dO6czMjJ006ZN9f79+7XWWt9+++36v//9b4n3GjdunF67dq3WWuuUlBSdk5OjV65cqceOHeu+1/vvv6+bNGmiz507V+x36iL/93748GGtlNLr1693l7muYbfb9aBBg/T27dsLfUeA/vbbb7XWWj/22GP6X//6V6H75P/t1lWcTq1PndJ6zRqt33tP68cf13rCBK27dNHa319r0ROVTSmtmzXTeuBArW+/Xetnn9V6/nytf/lF66gorbOyqvtpPBdgky7CptboFn5JLfHKwuZlo1HTRlzS7RIA1q5dyy233ILVaqVBgwYMGjSIjRs3EhwcTN++fWndujUAt9xyC2vXrsXb25vBgwfTvHFznNrJTbfcxKrVq7hixBUl3tfHx4dRo0YB0KVLF2w2G97e3nTp0oWY3LX01q5dy7Rp0wDo0KEDLVq04MCBA6xZs4YHH3wQgK5du7pb3r///jt79uxhQG7KRnZ2Nv3797/gd3BdblOuV69efPXVV+57f/nllwAMHTqUc+fOkZycTHBwsPu85cuXc88997jDK+Hh4Wzfvp1WrVpxySXyfU6aNIk5c+bw8MMPF3uvAQMG8OijjzJx4kSuu+46mjZtWmQ9hw8fTnh4+AWf53xatGhBv3793J8///xz5s2bh91u5+TJk+zZs6dQ78XHx8c9ZtOrVy9+/vnni75vXUApGfBt0ECkIPKjNZw+LSGiqCjZoqNlqchVq2DhQplclv9aDRtCs2Yyp8D12rSpbE2aQKNG0gsxCB75VWgPjeGDhBACAgJIycqL45d0bEmfLcpCkC2IIFtQgdm7mTmZKKUKZAJ5e3u7z7dYLNhy+7oWiwV7/mDpRaC1Zvjw4SxatOiiznPd22q1lvne5bnXE088wdixY1m2bBkDBgzgxx9/LPLcgDImkOc/7/Dhw7z66qts3LiRsLAwJk+eXOQs7/x/n6r4XmojLgPesKHMCD6f7GyZSBYTI9uxYzKT+NixvHGDfNHKAtds0iTPAbi2xo3z3terVzccQx14xIrHoizkOHPIcmQxcOBA3nnnHSZNmkR8fDxr1qxh5syZ7Nu3jw0bNnD48GFatGjBZ599xtSpU+nbty8PPvggZ8+eJSwsjEWLFjFt2jQaBjbE29ubnJwczmacJSMngwCfAIJtwfh7ly7XbeDAgSxcuJChQ4dy4MABjh49Svv27bnyyiv55JNPGDp0KLt27WLHjh0A9OvXj/vvv59Dhw7Rtm1b0tLSiI2Ndbe2LwbXvZ955hlWrVpFZGRkgdY9SIv7nXfeYciQIXh5eREfH0/79u2JiYlx1+Gjjz5i0KBBJd4rKiqKLl260KVLFzZu3Mi+ffto1qwZKSklZ0916NCBffv2FSp3fe9FyR4kJycTEBBASEgIp0+f5vvvv2dw/lFLQ5XhivfndpoLobVkEsXGimOIjS34Pjpa0kvPnSt8rlJi9Bs0yHM6rp5I/foFt3r1au7Ygkca/NKGdKoLq5K0gpSsFCZMmMD69evp1q0bSileeeUVGjZsyL59++jTpw8PPPCAe9B2woQJWCwWZsyYwZAhQ9BaM3bsWK655hoApk6dSteuXenRowez35tNYmYiadlpKKXQXFjz6L777uPee++lS5cueHl5sWDBAmw2G/feey9TpkyhY8eOdOzYkV69ZAJzvXr1WLBgAbfccgtZWVkAvPDCC2Uy+NOnT+eOO+6ga9eu+Pv788EHHxQ65i9/+QsHDhyga9eueHt7c9ddd/HAAw/w/vvv86c//ck9aHvPPfeUeK9Zs2axcuVKLBYLnTt3ZvTo0VgsFqxWK926dWPy5MmEhYUVOOfs2bPF9shc33vPnj3597//XWBft27d6NGjBx06dKBZs2bu8JfB81AKIiNl69at+OOysiR0dOIEnDwpr6dPy3bqlGwHDshr7r9FIYKDxfDXqyf3c72vV08Wr4mMLPgaFuYZK5jVuBWv9u7dS8eOHaupRoLWmh2ndxDoE0ib8DaVep8MewbpOenYrDaCbEE4nA5iU2Lx8/LD18sXm5cNb4u3x8zk9FSWLl1KdHS0eyyjOvCE366h9GgtGUVnzhTe4uJkO3s2731cnISdikIpMfoREZLeGhGR9/78LSxMXps1A1/fstW9ssTT6iRKKcL8wohLiyPHkYO3tXIUEJVS+Hv7FwjpOJwOrMpKUlYSiZmJgISYGgY2JMAnALvTjt1px9vibWb05mNc/mmkBkMpUEpa8sHBMqHsQmgNaWniBM6eldBR/vfnzkF8vLyeOiWL25w7VzBNNT8rVxac81AReKTB9/SQDkB9//qcSTtDXHpckfr4lYWPlw9NgpugtSbbkU2WI4tMe6bb6aRnp3M67TQgk7p8rD54W7yJ8IvAy+qF3WmX9CyLl+kVGAwViFIy2zgwUOYQlJacHEhMFGfg2hISRPW0ovFIg+/JWToufL19CbYFE5cWR8PAhlUuiubK4rF52Qi25Q2O+vv408jSiBxHDtmObHKcOaTnpBPhHwFAUmYS8RnxgKzk5dpcwm6Z9kzpRVisWJUVq8VqBN8MhkrE2zsv/l/ZeKTBryk0CGjAwfiDJGQkuA1qdeNl8SLQp/iZsoE+gXhbvMlx5rjDPznOHBTS2k/KTCI5K7nQNVuFiY57YmYi2Y5scQa5DsFqsbrDTlpr03MwGDwUY/DLQbAtGF8vX86knfEYg38hXL2C4ojwjyDEFoJd23E4HTi0o0B2S7Yjm9TsVLcWEIC31du9KMyJlBNk2DPyegjKis3LRqR/JCCZTRqd5yyU9CDMeIPBUPkYg18OlFLUD6jP0aSjpGanltiyrim4QjzFUT+gPvUD6ssKYNpZyCEE2YKwednczsLhdGB35k1Cis+IJ9tRMJXB39ufJsFNAIhNjnWvFuZyBr5evm5p6oycDJRS7n1mNTGDofR4ZHDWU+WRiyLCLwKLshSSPc4vj1xaapI8slLKPSicv8cQbAsm0j+SBoENaBzUmGYhzWgY2NC9v2lwU1qEtqBpcFMaBzWmQWADQn1D3fu9LF4oFHannQx7BslZyaTnpAMSLopNieVY0jFiEmOITogmKiGKuLQ4YmJiWLhwISdSTnAq9RRn0s5wLv0ciRmJZNoz3edn2bPIceTg1E63oyrP975gwQJOnDhR5vMNhqrEIw2+1nqJ1npqSIhnLwKutUahiPSPJCEjoVDL9WKpKoNfnbichJ+3n3smcX5ZiQaBDWgS3ITmIc1pGdqSNuFtqB9Q372/SVATGgU1okFgAyL9IwnzDcPP20/kkRd9gsPpINOeSWp2KvEZ8cSlx5GULg0Hh3ZwNOkoMYkxRMVHEZUQRXRCtPt7tzvtnEw5yZm0M5xNP0t8RjxJmUnuv6tTO90Ow+GUno0x+IaaRI0P6Tz5ZOGygQNhzBiZJTd9euH9w4bBVVdBcjK89FLBfed/Pp+YmJhC8rmz3pjFt999i7fVm+effb5U8sgvvviie6btyy+/bOSRK0geefyg8QXkkVNSU3A4HKxZvcY9X8EVinJqJ88/87z7e+/YqSMz3prBl598yQfvfEB2Tjbdenbj7bffxmqzMnnKZP7Y+AdKKa6/9XoaNWnExk0bufXWW/H392flryvJIMMdanJtQT5BeFu9cTgdbD6xGX9vf/y8/fDz8sPP249An0CTCWWoEmq8wa8ODh48yAcffEC/fv348ssv2bVjF9+t+Y7Y07FMGTuFK6+8EoANGzawZ88eWrRowahRo/jqq6+4/PLLefzxx9m8eTNhYWGMGDGCr7/+mhkzZjB79my2FSPwn5aWxtChQ5k5cyYTJkzg6aef5ueff2bPnj1MmjSJq6++mjlz5qCUYufOnezbt48RI0Zw4MAB3n77bfz9/dm7dy87duygZ8+egMgNvPDCCyxfvpyAgABefvllXnvtNZ599tkSnz8yMpItW7bw1ltv8eqrr/Luu+/y3HPP0aNHD77++mt++eUX/vznPxd6lnnz5hETE8O2bdvcWjqZmZlMnjyZFStWcMkll/DnP/+Zt99+262WWdS9Xn31VebMmcOAAQNITU3F19eXGTNm8Oqrr7J06VJAQi1btmxhx44dbsVMl1hdft78z5u8/8777rru3buXlUtXsumPTVi9rNx3331888U3dLm0CydPnGTzts04tZOEhASCQoL4dP6nzJw5k8v7XU5GTgap6ank6JwC4xt+Xn54WyUzavrq6YW+z1kjZ9EmvA0rolfw8c6PxRHkzqT28/bjvj73Ee4Xzs7TO9l2ahu+Xr4FtsuaXoaP1Ydz6edIzU51l9u8bPhYfYwzMbip8Qa/pBa5zVby/uDgC7foiyK/fK5LHrlxcGNS7an0G9CvVPLI9XKTbidOnMiaNWu49tprS7ynkUeuGnnkFStWsHnzZvr06QNARkYGDRs05Jqrr+Hw4cM89benGDt2LCNGjMBiseBt9cbHywcAP28/moU0K3C9/APaPlYfZg6fSXpOOhk5GWTaM0nPSXeHrOoF1KNbg25k2bPIsMv+uLQ4d8rs/nP7+WLPF4V0lRZdvwgfqw9LDizhy71fFnqmxTctxsvixcc7PubXI79KppZVsrUCvAN4cqB0k1ceXkl0QrTbUdisNgJ9Armq9VUAHIo/RFp2mnvcxtvijZ+3nzsDy6mdKJQZRPdgqszgK6U6Ag8BkcAKrfXbVXXviqYo2d0gWxC+Xr5k2jPd/+QXkke+GIw8ctXII2utmTRpEi8V0RLYvn07P/74I3PnzuXzzz9n/vz5F7xe/r+5RVnoENmh2GO7NuhK1wZdi91/Q6cbuL7j9eQ4c8i0Z7o31xyIoa2G0ja8LVl2mX3tmontEvtrGNiQNuFtpNyeRZYjq0AG1a4zu1hzdA1Z9iy3Uwn3C3cb/IU7FrLpZMGB/MaBjXln/DsA/GPFP9gdt9vtDGxeNtqEteHpK58GYM6GOZxJOyNO0uqDj9WHlqEtubbDtQAsO7iMjJwM9z4fqw8NAhvQqV4nAA6eO4hSyj173OZlc4fFDKWjVAZfKTUfGAec0Vpfmq98FPA6YAXe1VrPKO4aWuu9wD1KKQvwIVBjDX5+8ssjW9ItbFy/kf+++l+OHT52UfLIULJMb2nrYuSRi6c08shXXXUV11xzDY888gj169cnPj6elJQUAgIC8PHx4frrr6d9+/bcdtttAAQFBV3wvhWJy+D5WH0KzLAGaB7SnOYhzYs9d1jrYQxrPazY/dMum8a0y6ahtcbutJPtyC7gEO7seSfXZ17vnsWd5cjCx+pT4Pqd6nUi25Ht3lytf4AcZw5pOWlkZWS55cWz7HlylF/u+ZIz6QWz3fo37e82+NNXTy80KXBIyyE82v9RAG76n4ydeVvyHMrQVkO5sfONMl6z6nl3ubfVG5vVRq/GvejbpC/ZjmyWHljqdiSu1xYhLWgS3IQcRw5Hko5gs9oKnG/zspWYxuxplLamC4DZiKEGQCllBeYAw4HjwEal1LeI8T+/eXSH1vqMUupq4F7go3LW22M4Xx75oacfgtwwcVnlkXv27Flo0LY0GHnk8ssjL1y4kBdeeIERI0bgdDrx9vZmzpw5+Pn5MWXKFJy5Sy65egCTJ0/mnnvuwc/Pj/Xr1+PnV/Nbm0opvK3ehUQBmwY3pWlw0eEzwN0TKI6H+z1c4v554+eR48xxO5RsR3aBOjw+4HEycjLEWdjFaTQKbOTeP7bdWLLsWW5JkWxHNmG+8juwO+2kZqfmOSNnNjmOHCL8I+jbpC/pOem8v+39QnWa1G0SN3S6gbPpZ3nkx0cK7b+n1z2MvWQsMYkxPLH8CbezcIXEJnadSO/GvTmWdIxPdn5SoPfickjNQpoRlxbH1lNb3eUuR1PRlFoeWSnVEljqauErpfoD07XWI3M/Pwmgtb5gVFwp9Z3Wemwx+6YCUwGaN2/e68iRIwX2e7rEbGxyLCdTT9I0uGmB/HND9WLkkQ0lobUmy5Hldgg5DumBhPqGEuobSqY9k+2nthfovWQ7sunaoCutwloRlxbH4n2L3Y7I5XgmdJxA1wZdOXDuAP9d/1+3s3Gd/9QVT9GnSR/+OP4HL/z6grs+E7tM5OZLby7z8xQnj1weg38DMEpr/Zfcz7cDl2mtHyjm/MHAdYAN2KG1nnOhe3qqHn5JaK2JTogmITOBZsHNaBDYoLqrZPAQPP23a6g+sh3ZJGclux1BsC2YcL+LX4/ZRbXr4WutVwGrSnNsTZBHLg6lFK3CWqETNMeSj7nlFwwGg6E4fKw+BcY7KovyJOjGAvlz0JrmltV5LMpC67DWhNhCOJp0lLi0uOquksFgMJTL4G8E2imlWimlfICbgW8rolI1RVqhJCzKQpvwNoTYQjiSdISz6Weru0oGg6GOUyqDr5RaBKwH2iuljiul7tRa24EHgB+BvcDnWuvdFVGpmiSeVhIuox9sCyYmMcYYfYPBUK2UKoavtb6lmPJlwLIKrVEtw6IstA1ry6GEQ8QkxpDtyKZRYCMzG9FgMFQ5HimyURtCOvnlkS0WMfrhfuGcSDnBofhDBSa0uKhJ8siVQVmVJ1etWsW6desu+rxNmzZVa5qmwVDVeKTBrylord0TcS6ExWKhVWgrmgU3Izkrmb1xe8nIyShwTF2QRy6Jkgy+w+EoshxKNvglST/07t2bN9544+IqaTDUYDxyTnBp0zIf/uFhtp3aVqH37t6wO7NGzSp2f1HyyLNnz+b7779HKcXTTz9dojxyg8AGfPfVd7z00ktorRkzZgxvvPZGjZFH3rhxIw899BBpaWnYbDZWrFiBt7d3kfddsGAB3377Lenp6URFRTFhwgReeeUVHA4Hd955J5s2bUIpxR133EGzZs3YtGkTEydOdM9a7dixIzfddBM///wzf//737n55sITUWJiYpg7dy5Wq5WPP/6YN998k/feew9fX1+2bt3KgAEDuPnmm3nooYfIzMzEz8+P999/n/bt27Nq1Sq3wub06dM5evQo0dHRHD16lIcffti0/g21Do80+FrrJcCS3r1731XddSmK8+WRt23bxvbt2zl79ix9+vS5oDzy9Kens37DehJIYMr1U/i/hf/Hiy+96PHyyNnZ2dx000189tln9OnTh+TkZPz8/Hj99deLvC/Atm3b2Lp1Kzabjfbt2zNt2jTOnDlDbGwsu3btAiAxMZHQ0FBmz57Nq6++Su/eefNFIiIi2LJlS7F/i5YtW3LPPfcQGBjI3/72NwDee+89jh8/zrp167BarSQnJ/Prr7/i5eXF8uXLeeqpp9zKnvnZt28fK1euJCUlhfbt23PvvfeWWdfIYPBEPNLgl7aFX1JLvDIpSh7ZarXSoEEDBg0aVCp55CYNm9BIN+JPN/+JVatXcfnwy0u8pyfII+/fv59GjRq5pYNd4mjF3RfgqquuwjUW06lTJ44cOULnzp2Jjo5m2rRpbqnh4nD1li6WP/3pT1itohKZlJTEpEmTOHhQ1BZzcnKKPGfs2LHYbDZsNhv169fn9OnTxUovGww1EY+M4Xv6oG1pZXcvJI9sURYi/CMI9Q3F7rTj1E6OJR3D4Swcr65MeeRt27axbds29uzZw3vvvVemaxWHq56QJ3EcFhbG9u3bGTx4MHPnzuUvf/lLseeXReL4/POeeeYZhgwZwq5du1iyZAmZmZmlrqvBUJvwSINfkxg4cCCfffYZDoeDuLg41qxZQ9++fQHc8shOp5PPPvuMK664gr59+7J69WrOnj2Lw+Fg0aJFjLhqBJ3rd8bb25vYxFh2x+0mKfPi5yC4JIqBIuWRgULyyL/99huHDh0CJGzkapkXRfv27Tl58iQbN24EICUlBbvdXux9i+Ps2bM4nU6uv/56XnjhBXfI5kJSw7Nnz2b27NmFyi90XlJSEk2aiPLgggULij3OYKjtGINfTiZMmEDXrl3p1q0bQ4cO5ZVXXqFhQ1HJdMkjd+zYkVatWjFhwgQaNWrklkfu1q0bvXr14pprrsHL4sU9d9/DpJGTePK+JzkYf5DDCYfJcRQdfiiK++67D6fTSZcuXbjpppsKyCOnpqbSsWNHnn322SLlkbt27Ur//v2L1It34ePjw2effca0adPo1q0bw4cPJzMzs9j7FkdsbCyDBw+me/fu3HbbbYWkhrt3715gYNnFvn37iIiIKFQ+fvx4Fi9eTPfu3fn1118L7f/73//Ok08+SY8ePUyr3VCnKbVaZlWSL4Z/18GDBwvsqwuKg07t5GTKSU6lnkIpRcPAhjQIaIDVYq3uqlUr48aN46uvvsLHx+fCB3sgdeG3a/AMilPL9MgWvqfH8Csbi7LQJLgJnep1IsQWwomUE+w8s5NTqadKnfdfG1m6dGmNNfYGgyfgkVk6BsHP24824W1Iy04jNiWW48nHOZ16msZBjYn0jzTyDAaD4aIwBr8GEOATwCURl5CclUxscixHko5wMvUk9QPqE+kfWaPW1DQYDNWHsRQ1iGBbMEGRQSRlJXEq9RTHk49zIuUEEX4R1A+oj593zV9P1WAwVB4eafBr8opXlY1Syr3OZnp2OqfTT3M2/Sxx6XEE24KpH1CfEFuICfcYDIZCmEHbGoy/jz+tQlvRtUFXGgc1JiMng0Pxh9h+ejtHk46Smp2KJ2ZhGQyG6sEjDX5tIL88cmkpq1qmt9WbxkGN6dKgC23D2hLkE0RcWhz7zu5j15ldnEg5QaY9b3apkUfOIyYmxj0pzWCo7RiDXw4uRh65NJRXHtmiLIT6hdImvA3dGnajZWhLfKw+nEg5wa4zu9h1ZhfHk4/jcDo8suVfGfLIF8IYfENdwiNj+BfDk8ufLFQ2sMVAxrQbQ5Y9i+mrphfaP6z1MK5qfRXJWcm89OtLBfa9NOylQsfnp7zyyBaLhUWLFvHiiy+itWbs2LG8/PLLlSqPnJSaxKTJk9i5YyfN2zQnITWBA+cOEJEQwda1W5n54kyys7JrjTxyhw4duOeeezh69CgAs2bNYsCAAaxevZqHHnoIkLGQNWvW8MQTT7B37166d+/OpEmTeOSRR0r8+xsMNZkqNfhKqQBgNTBda720Ku9dkZRXHvnxxx9n8+bNhIWFMWLECL7++mtmzJhRafLI7817j/DgcKIORLFl2xb69u5LoE8gR04c4d///jevf/w6ESERfPTWR7z4yov8a/q/iqxDTZFHvvXWW3nkkUe44oorOHr0KCNHjmTv3r28+uqrzJkzhwEDBpCamoqvry8zZsxwa+IbDLWdUhl8pdR8YBxwRmt9ab7yUcDrgBV4V2s94wKXehz4vIx1LZKSWuQ2L1uJ+4NtwRds0RdFRcgj16tXD4CJEyeyZs0arr322hLvWVHyyD2796Rr1640CW7CyeiTHDl0hHuuuwen00lWdhZdenVh66mtpOekczr1NIkZiQT4BOBt9a4x8sjLly9nz5497s/JycmkpqYyYMAAHn30USZOnMh1111npI8NdY7StvAXALOBD10FSikrMAcYDhwHNiqlvkWM//lW9A6gG7AH8C1flaufipJHvhgqQx4ZYMTwESxatAgAp9NJak4qKVmiPBmfEc+hBFHStFltHE8+To4zh/ScdPy8/Er1PCXJI//444/MnTuXzz//nPnz5xd5flnkkZ1OJ7///ju+vgV/ak888QRjx45l2bJlDBgwgB9//PGir20w1GRKNWirtV4DxJ9X3Bc4pLWO1lpnA58C12itd2qtx523nQEGA/2AW4G7lFJF3lspNVUptUkptSkuLq6sz1VlVIQ88qBBgwAx6sUtzlHaupRHHjkjI4NTR07RJLgJ/t7+tI9sT4eIDjQNboqftx+RzSKJPRHLFz9/wdZTW9l0eBPR56LpeVlPPvjoA7TWHiGPPGLECN588033Z1eYLCoqii5duvD444/Tp08f9u3bd8F7Ggy1ifLE8JsAx/J9Pg5cVtzBWut/ACilJgNntdZFprdorecppU4C4318fHqVo35VwoQJE1i/fj3dunVDKeWWR963b59bHtk1aDthwgQsFotbHtk1aHvNNdcAMHXqVLp27UrPnj0LDdqWhvvuu497772XLl264OXlVUAeecqUKXTs2JGOHTsWKY+clZUFwAsvvMAll1wCSNZPoC2QQJsM4uowzWeffsbDDz1Meno63r7ezPl0DkNvHMqGJzfQrlM7vL28mTF7Bsn2ZLLsWUVmA8XGxjJlyhR3htP58siuQdvz2bdvn3t1rvyMHz+eG264gW+++YY333yTN954g/vvv5+uXbtit9u58sormTt3LrNmzWLlypVYLBY6d+7M6NGjsVgsWK1WunXrxuTJk82graFWU2p5ZKVUS2CpK4avlLoBGKW1/kvu59uBy7TWD1RU5Xr37q3PzwU3ErOehdaaTHsmaTlppOekk56TTkZOBg6dl0bp6+WLn5cf/t7++Hn7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\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "%matplotlib inline\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "plt.semilogy(train_loss_unconstr, color='blue', label='robot model no constr, train')\n",
    "plt.semilogy(test_loss_unconstr, color='blue', linestyle='--', alpha=0.7, label='robot model no constr, test')\n",
    "plt.semilogy(train_loss_constr, color='green', label='robot model constr, train')\n",
    "plt.semilogy(test_loss_constr, color='green', linestyle='--',alpha=0.7, label='robot model constr, test')\n",
    "plt.semilogy(train_loss_mlp, color='orange', label='mlp, train')\n",
    "plt.semilogy(test_loss_mlp, color='orange', linestyle='--',alpha=0.7, label='mlp, test')\n",
    "plt.legend()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.9"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}
